Alrighty, in this lecture, we're going to be scripting the system that will set up and be responsible
for handling our players spectating system.
So to do that, we're going to have to go into starter player and go into starter player scripts and
go ahead and open up the handler for our main guy.
From this point we can scroll down and if you remember, when we are told that we have died and we display
the died frame on the screen, we need to go ahead and call our start spectating function to start spectating.
Currently, this function is empty, so we need to go ahead and fill out the functionality for this.
So to start spectating, the first thing that we need to do is refer to our spectate frame and set the
visibility equal to true.
So that way it appears on our screen.
The next thing that we would probably have to do is enable night vision on our screen.
So that way we are able to see in the dark.
So if you remember we have our camera manipulation bindable that we have inside of, I believe, replicated
storage that we can use to communicate with our camera manipulation handler to go ahead and enable night
vision.
So if you remember here we have our camera manipulation Bindable, which listens to the exact same function
here.
And we have the action to be able to enable night vision on the client.
So that means we're going to have to make a reference to this bindable inside of our main guy handler,
so we can go up and we can go ahead and call this manipulation camera Bindable.
That's in replicated storage, dot events, dot bindable, dot camera manipulation or manipulate camera.
And we're also going to go ahead and get our camera manipulation enum as well.
So camera manipulation enum that's in replicated storage, dot modules, dot enums dot camera manipulation
enum.
And inside of our start spectating function we're going to use the manipulation camera Bindable to fire
and let our local script know that we would like to use the camera manipulation enum action to client
of toggle night Vision.
And remember we have to pass a boolean called Set on and we want it to be equal to true.
That way we are enabling our night vision from this point.
The next thing we would need to do is actually make sure that there are players on our map to spectate,
so we could create a function to get all of the alive players or the players that we can spectate.
So we can call this function get alive players.
And we would refer to our alive team and get all of the players on that team.
So we're going to have to make a reference to the team service up here.
And then we could go back down.
And from here, all we need to do is return all of the players that are on our alive team.
So we can go ahead and get all of our live players from our Get Alive players function.
And as long as the number of alive players is greater than zero, then we can start spectating.
So that means we're going to have to make a reference to our camera and change some properties inside
of our camera.
And this is because we don't want our camera to be stuck in first person.
We actually want to zoom the camera out.
So that way our players can actually look around the map and rotate our camera around the players that
they're spectating.
So let's also go back up here and we can go ahead and create a variable to refer to our current camera.
And that's in the workspace dot current camera.
And I'll just call this current cam just to make it a little shorter.
And while a live players is greater than zero, then we're going to refer to our current cam and set
the camera type equal to the enum dot camera type of custom.
That way our players can actually control the camera, and we're also going to set on our local player
a property called Camera Mode by default.
If you remember, we're stuck in first person.
So we need to change this camera mode to the enum dot camera mode of classic.
That way players aren't stuck inside of first person.
From this point, we're going to use our manipulation camera bindable again and fire, and we're going
to use the camera manipulation enum dot two client.
And we want to give the action of setting the camera occlusion mode, because while we're spectating,
we don't want our camera to be stuck, you know, inside of parts.
Because for example, if the player were trying to spectate is like, let's say inside of a locker,
well, our camera is going to be stuck colliding with the walls of the locker, and we're not going
to really have a good view of the map or of our player.
So we need to change the occlusion mode of our camera to a different one, and the mode that we would
like to change it for.
Remember, we have to pass a table with the occlusion mode.
And that's going to be the enum.dev camera occlusion mode dot Invisi cam.
And if we actually read that again, let me retype that in Physicam any objects between the camera and
its subject become translucent locally.
So this means our camera is able to pass through objects, and we can actually see our player through
different parts because those parts become slightly transparent.
So when we give this action to our camera manipulation handler, if we go ahead and look here, we're
going to change the camera occlusion mode to whatever.
And we get that occlusion mode.
And then we use our camera manipulation event and fire to the server to change the camera occlusion
mode for us, because we aren't able to change the camera occlusion mode from the client, we have to
do it from the server.
So that means we're going to have to create or script the functionality for listening to our manipulation
camera event.
So we can go ahead and do that inside of server script service.
We'll go into the server and we'll open up one of these services.
We could do it from our player join service.
That would probably be the easiest.
So inside of here we can make a reference to our manipulate camera event and that's inside of replicated
storage, dot events, dot remotes, dot manipulate camera.
And we're also going to make a reference to the camera manipulation enum.
So we'll acquire from replicated storage dot modules, dot enums dot camera manipulation enum.
And then we can go down to our initialize function.
And we can go ahead and listen to this event.
So manipulate cam event dot on server event.
And we want to connect a Lambda function to this.
Get the player that fired this event, get the action they wish for us to do and the arguments to go
with that action.
And the only action we care about in here is changing the player's occlusion mode.
But we don't want to give any exploiters the ability to do this.
So the first thing we want to check is to make sure that the player is actually on the dead team.
So we know they're spectating.
So let's go ahead and make a reference to the team service up here.
And then we can go back down.
And we want to check if this player's team is not equal to the dead team.
So if they are on the alive team, then we don't care about them firing this event, we're just going
to return out of the function.
Otherwise what we can do is we can check if the action they supplied to us is equal to our camera manipulation.
Enum dot two server dot change camera occlusion.
So again remember inside of our camera manipulation handler we're firing to the server with the action
change camera occlusion.
And we're giving them the occlusion mode that we would like to change our camera to.
Now, when we get past this action, the first thing we actually want to verify is the information coming
from the client.
Because an exploiter could be messing with whatever is inside of our ARGs table.
Who knows, it could be passing whatever here, so we don't want to cause any errors on the server.
We first want to verify the information being passed before we actually start acting on it.
So what we could do is we could use our args matches type function in here, up here to verify the type
of our args table and any value inside of our args table.
So if arg matches type, we're going to pass args and check to see if this matches table.
So if this does not match the argument of table then we're just going to return because we don't care
about this player.
They're obviously just goofing around with our remote event.
Another thing we want to check is the type of our args occlusion mode.
And if this is not equal to the type of enum item, then we obviously know that again, an exploiter
is probably messing with our remote event, so we're just going to return.
Otherwise, we want to make sure and check if the arg dot occlusion mode dot enum type is actually equal
to the dev camera occlusion mode.
So if it is not equal to the enum.dev camera occlusion mode, then again we probably have some exploiter
messing with our remote, so we're going to return.
Otherwise, once we have verified that all of the information passed here is correct on our player,
we're going to set the dev camera occlusion mode equal to the args dot occlusion mode that was passed
to our remote event, and that's all we need to do there.
So back inside of our main guy handler, after we tell our camera manipulation handler to change our
camera occlusion mode to the occlusion mode of Invisi cam, our camera should be able to pass through
parts, which now means what we're going to do inside of our local player is set the property of camera
max zoom distance equal to something we could do like seven studs.
And we also want to change the camera minimum zoom distance.
And we can reduce it to something like two studs.
If we put it at 0.5, that means players can zoom all the way and go into first person mode, which
we don't want.
So instead we're giving them a little bit of a range that they can zoom in and out of to spectate the
players.
Otherwise, the next thing we would want to do is create a variable to get the player that we're currently
spectating.
So we can call this spectating player.
And we would get it out of our live players table, and we would pass an index of the player that we're
viewing.
So we should create a variable actually up top here.
And we can just call this variable something like, um, viewing player.
And all this is going to do is keep track of the current index that we're currently viewing at.
So when we get all of the alive players in the game and we hit the plus button to go to the next player,
or we hit the minus button to go to the previous player, we're going to be updating the value inside
of our viewing player variable, so we can go back down and we can pass viewing Player.
And by default it's going to be number one or the first index within our live players table.
And what we can do is we can refer to our current camera, and inside of there we want to change the
camera subject property and set it equal to the spectating player dot character dot humanoid.
Because by default, the camera subject is going to be set to our own player's character, but we want
to change it to the other player's character that we're going to be spectating.
After we do this, then we can go ahead and refer to our spectating frame, and there's a text label
in there called name for player.
And we want to change the text of that equal to the current player that we're spectating.
So we'll just pass the name of that player.
From this point.
Another important thing we want to listen to is when this player gets jumpscared.
So remember we set an attribute on that player when they get jumpscared.
And when that happens, what we want to do is we want to stop spectating this player because they've
just died.
So that means we're going to have to listen on the spectating player.
And we can use git attribute change signal.
And the attribute we want to listen to is when they get jumpscared.
And we can connect a function to this.
And when this player gets jumpscared, we can um, have a function to, uh, basically change our camera
to view a different player, or we can just have our camera directly view our AI monster squid, where
that's roaming around the map, whatever is easier.
So that means we're going to have to get the connection that gets returned from our connect function
and disconnect and connect to this, uh, whenever.
Because we don't want to constantly listen to this player every single time this, uh, attribute changes
on them.
So we could create a table and we're going to call this table, we could call it like viewing player
connections, set it equal to a table, and then we can go back down.
And then inside of our viewing player connections table, we can create a new key value pair.
And the key for this would just be, um, our spectating player dot name equal to the spectating player.
And we're going to be listening to when they're jumpscare attribute changes.
And when that does we can have a function to control exactly what we want to do when our spectating
player gets jumpscared.
So we can call this function on spectating player jumpscared.
So this will listen to when they die.
And we can get the player that gets jumpscared here.
And all we're going to do down here is just call our on spectating player jumpscared function and pass
the spectating player.
And what we can do in here is that after our spectating player gets jumpscared, we first want to check
if they have a key value pair inside of our viewing player connections table.
So we'll pass this player's name.
So if they have a key in there, then what we need to do is refer to viewing player connections, pass
player dot name and we want to disconnect the function on that is currently listening to.
When their attribute changes, we disconnect that and then we want to change the player dot name inside
of here equal to nil.
So we get rid of that key value pair completely.
And afterwards we want to refer to our current camera and change our camera subject.
And what we could do is we could try to get all of the alive players that are currently in our game
again and try to see if there's any left and set our camera subject to that, so we could get a live
players from our Get Alive Players function.
And basically we'll just set camera subject equal to.
What we could do is check if the number of live players is actually greater than zero.
So if number of live players is greater than zero, then what we could do is set our camera subject
equal to a live players will pass one.
Get their character and then get their humanoid.
Otherwise, if we do not have any alive players left for some reason, then we can just set the camera
subject of our camera equal to maybe, let's say, our Squidward that's roaming around the map.
So inside of the workspace characters folder, we can find First Child, which would be our handsome
Squidward AI, and we can go ahead and get his humanoid.
So if this condition evaluates to true right here, then the spectating player is going to be whoever's
left alive.
Otherwise, if there is nobody that's left alive, then the other spectating person we're going to be
viewing is our handsome Squidward.
So the next thing we can do is refer to our spectate frame.
And set name for player dot text equal to.
And again we're going to copy this.
If the number of alive players is greater than zero, that means we can go ahead and set the text equal
to the name of that spectating player.
So that means we can put an and statement here, get a live players number one and then just get their
name.
Or we'll just set the name equal to something like Squidward.
So if there's any live players left, we spectate that.
A live player, we set the text equal to their name.
Otherwise instead we just go ahead and spectate our Squidward that's roaming around the map.
And then once that's done, we can set viewing player equal to one.
And I think that's all we need to do for this function.
So I think the next important things that we're going to have to do is listen to when we press different
buttons within our spectating frame.
So for example, if we want to spectate the next player, then we'll spectate the next player.
If we want to spectate the previous player then we'll spectate the previous player.
So when we click the next button inside of our spectating frame to move to the next player, the first
thing we of course need to check is whether or not we have any alive players left.
So we're going to get the current alive players.
And if the number of alive players that we have is, let's say, equal to zero, then we're just simply
going to return because we don't care about them hitting the next button.
Otherwise, what we could do is refer to viewing player and increment it by one.
And if the viewing player's value becomes greater than the number of alive players, then all we're
going to do is set viewing player equal to one.
So if we keep clicking the next button, eventually we'll want to reset it and put it back at the value
of one.
And then we can get the spectating player, which is equal to our live player's, and get whatever player
at the current viewing player index.
And then inside of our current camera, we'll set the camera subject property equal to the spectating
player.
Dot character dot humanoid.
And then we're also going to update our spectate frame dot name for player dot text equal to the spectating
player's name.
And then from this point, we actually want to do the exact same thing that we did inside of this function
here.
We want to listen for when this player dies.
So we'll just copy this, paste this down here we get the spectating player and listen for when they
are jumpscared.
And when that happens then we move on to the next player.
Otherwise we can just basically copy all of this and do the exact same thing when we hit the previous
button, but instead this time we want to decrement our viewing player value by one, and we want to
ensure that if our viewing player number becomes, let's say, less than the number of live players,
then we can set our viewing player value equal to the number of alive players.
So basically, if we keep decreasing the value, eventually we go back to the top and then continue
decreasing from that point.
Other than that, we do the exact same thing by changing the camera subject equal to the current spectating
player, and we listen for when that spectating player gets jumpscared.
So I believe now that's actually everything we needed to do for our spectating system.
The only thing left to do is actually test it by going to the test tab and starting a server with multiple
different players in there.
That way, we can actually fully test out whether or not our spectating system is working or not.
So let's go ahead and test this out and I'll see you there.
All right.
As you can see, I've got my three player test set up and I'm about to enter the basement, which should
teleport all of my players down there.
Beautiful.
And basically I'm going to have one of my guys hide inside of a locker to see if we can actually spectate
him.
So we'll put this guy inside of a locker.
And then for this guy, we could also put him inside of a locker as well.
Let me actually just pick up this flashlight off the floor real quick, and then let me slap this guy
inside of a locker.
And then with this guy, we're just going to have this guy here on the right die and see if we can actually
spectate these players that are currently inside of these lockers.
So that means we should go look for our Squidward and have him kill us just to see if we get the spectating
system open.
There we go.
We died and hopefully we should start spectating and perfect.
It says we are currently spectating player number two.
As you can see, we are able to look around him.
And since we change the occlusion mode of the camera, as you can see, when I pass my camera through
this wall, it becomes transparent and we can go ahead and look around at our player.
Perfect.
And if I hit the next button, we should move to the next player.
There we go.
We're at player number three.
And if I hit this again it should go back to the other player.
Perfect.
So our spectating system is working great.
The next thing we need to check is when our spectating player gets jumpscared.
So this guy inside of the locker here, what we're going to do is we're going to have him hop out and
let's see what happens to our spectating system when this guy gets jumpscared.
So we go back to this guy and we're going to exit the locker and let me grab the flashlight off the
floor right here.
And we're going to look around, try to find the Squidward to jumpscare us.
As you can see, it looks great on our spectating system.
We can actually see the entire map because we have our night vision on.
But let me try to find the Squidward here real quick.
I think I hear him over here somewhere.
Where are you at, Squidward?
There he is!
So let's get Jumpscared and see what happens.
So he did get jump scared and we did move our player back to the other spectating player.
But of course there looks like there was a little bit of a delay there.
I'm not sure why.
Maybe we accidentally set the attribute of jump scared a little too late on the player.
So what we can do is we can check to see when we set that attribute on the player when they get jump
scared.
But other than that, it appears that our spectating system is actually working quite well.
So the last thing we can do here is have this guy die and see what happens to our spectating system.
So we're going to have him come out of the locker, turn on his light and let's have him walk around.
All right.
And there we go.
As you can see, our spectating system appears to have moved back to Squidward here, but unfortunately,
it does appear that our spectating system for this guy hasn't moved back.
So let's go ahead and figure out why exactly the spectating system didn't work for our other guy.
There might be a little bit of an error in our logic.
Okay, so I think a better system that we could do for our spectating system is have our spectating
player variable be a global variable in our entire script, and instead update this variable each time
we change the player that we're spectating when we hit the previous or next button.
And we want to do this to check if we had any previous connections on them.
And when we go to spectate the next player, we want to disconnect any of those previous connections
because we're no longer spectating that particular player.
So what we can do is we can copy this here and I'll go back up.
And what we'll do is we'll create this variable up here and we're not going to initialize it to anything,
or we can just set it to nil as default.
And then we can go back down.
And what we can do here is we can update our spectating player equal to the current alive players.
So again we'll do that down here as well.
And before we change our spectating player we want to do is we want to check if there was a previous
spectating player.
So if we had a previous spectating player before we update the variable, we want to check if the spectating
player has a key value pair inside of our viewing player connections table.
So if viewing player connections table, we pass spectating player dot name.
Then we will want to disconnect that.
So we'll copy this, paste that in there, disconnect that and then paste it again and set it equal
to nil.
And then we can copy this right here and do the exact same thing for our next script here.
So we update our spectating player to disconnect any previous spectating.
And then we also want to do the exact same thing when we actually start spectating.
So we're going to set spectating players equal to this.
And we're also going to check if there was a previous spectating player just in case.
And now that means inside of our own spectating player Jumpscared function.
It's not going to work very well when we change our camera subject to spectate a different player.
And this is because we're not listening to this particular player when they get jumpscared.
And that's not possible to do because we're doing it within this function itself.
So to avoid this kind of conundrum, what we could do is just set the camera subject directly to our
Squidward instead, and instead just set the text equal to Squidward.
And we don't have to sit there and worry about our current viewing player, so we'll just do that instead
and have our current spectating player at our Squidward.
And then we can also actually just keep our viewing player and reset that value back to one.
So that way if our player doesn't want to spectate Squidward, then they can hit the next button or
the previous button on their spectating frame, and that way it actually moves to the next player.
And because we're moving to the next player, we're actually able to listen for when that particular
player gets jumpscared.
So now this system actually should be working quite good.
Now we can go ahead and start up another three player server and see if our spectating system is working
fine.
All right.
We got everything set up again.
We got our two guys chilling inside of their lockers.
So what we're going to do now is have our guy in the center here die and see if we can go ahead and
start spectating those dudes in the lockers.
So we'll try to find Squidward here.
There he is.
All right.
We have died, and let's see if we start spectating.
Okay.
Perfect.
Here we are spectating player number two.
And we can switch between these two different players.
So.
It does look like when I go down and hit the previous button, I'm not moving to the next player, which
is actually kind of interesting.
So we'll obviously have to figure out why our left button isn't working, but that's just a little minor
arrow.
We can fix in a moment, but let's have um, I actually don't know which guys and which locker.
Let's see if we can have this guy come out of his locker.
There we go.
Let's have this guy die and see what happens.
So we're going to have him roam around and he'll run down here.
We're trying to find Squidward here.
There he is.
Oh, fortunately, he did disappear, but he's coming back.
There we go and we immediately start spectating our Squidward friend here.
Perfect.
So it doesn't look like we have any problems with that.
And if we hit the next button, we should go to our other player.
And there we go.
We're back to player number three chilling inside of our locker.
So let's go ahead and have this guy die.
So we'll have him exit out of the locker.
Have him look around the map.
And let's see what happens.
Perfect.
And now all of our players are spectating.
Squidward, everything is working well.
We don't need to worry about our last guy not spectating anyone, because by this point, all of the
players in our game have died and that means we would have to, you know, end the game because everybody
died, everybody failed.
That should actually be pretty easy to do.
All we would have to do is listen for when the number of players on our live team reaches zero, and
then we would teleport all of our players back to the lobby.
So actually, let's go ahead and do that real quick.
And let's also fix our, uh, little left button that's not resetting correctly.
Okay, so in our previous button here inside of the lambda function, I think our problem is we are
comparing the viewing player number to the wrong value here.
Instead of looking at the maximum number of alive players, we just need to pay attention to when our
viewing player number goes below one, which is the minimum index that we can have when our viewing
player number goes below one.
Then we set the viewing player number equal to the number of alive players.
So that should fix our problem right there.
And then the other thing we can go ahead and script is when all the players in our game die, we can
go ahead and teleport all of them back to the lobby.
So what we could do is we could go to our game service, and inside of here we can just scroll down
and inside of the initialize section, what we can do is we can create a little section to listen for
when all of our players have died.
So what we could do is create a section here to listen for the live team.
When a player is removed and we'll connect a lambda function to this.
And all we can do is check if the number of people in the alive team get players.
If the number of people in there is zero, so everybody has died, then we can just simply call our
teleport losing players to lobby function, which will handle everything else for us by displaying on
the screen that everybody failed, and then we teleport them back to the lobby.
So that should be good to go there.
And our spectating system should also be complete and fixed up.
So let's go ahead and test this out one more time to verify that everything is working.
Okay.
So I got my two guys back in their lockers again.
And we're going to get our center guy here to die.
All right, we're dead.
Hopefully we start spectating now.
And there we go.
We're spectating.
Player one move to the next, player spectating player two.
And we can switch between them.
And then it should work the same way when we click the previous button.
So we click that and perfect we're moving between the two players.
So let's go ahead and go to player number two and check to see when he dies.
So when we go and exit the locker and we look around we're roaming around, see if we can try to get
us to die here.
Okay.
Perfect.
We are spectating Squidward here, watching him roam around, and if we click, we should move to the
next player.
Which we do.
And our other guy is also successfully spectating this guy as well.
So let's go ahead and have him exit the locker.
And when this guy dies right here, we should end the game because all of the players are dead.
So let's go ahead and see what happens.
We're going to have this guy roam around, try to find Squidward here and here.
I'm not sure where he's at.
I think we're getting close to him here.
Areas.
And there we go.
Everybody is dead.
You failed.
Unfortunately, you failed to win this time.
Don't give up.
Try again.
So all of our players failed.
Of course, we can't teleport while we were in testing.
But as you can see, everything's working great.
Everybody dies, the game ends.
And our spectating system so far is seeming to work great.
So the next thing we need to fill out in our spectating system is giving the ability for players to
respawn and for them to respond.
They can click that respawn button inside of the spectating system, which will prompt them to purchase
a developer product to respawn.
And that's what we're going to do in the next lecture.
So we'll go ahead and see you there.